﻿using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Caching.StackExchangeRedis;
using StackExchange.Redis;

namespace StudyCompanion.Commons.Cache;

/// <summary>
/// </summary>
public class RedisCacheService : ICacheService, IDistributedLocker
{
    private readonly IDatabase _cache;

    private readonly ConnectionMultiplexer _connection;

    private readonly string _instance;

    private readonly JsonSerializerOptions _jsonOptions;

    private readonly RedisCacheOptions _options;

    /// <summary>
    /// </summary>
    /// <param name="options"></param>
    /// <param name="jsonOptions"></param>
    public RedisCacheService(RedisCacheOptions options, JsonSerializerOptions jsonOptions)
    {
        _connection = ConnectionMultiplexer.Connect(options.Configuration);
        _cache = _connection.GetDatabase();
        _jsonOptions = jsonOptions;
        _instance = options.InstanceName;
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public string GetKeyForRedis(string key)
    {
        return _instance + key;
    }

    /// <summary>
    /// </summary>
    public void Dispose()
    {
        if (_connection != null)
            _connection.Dispose();
        GC.SuppressFinalize(this);
    }

    #region 验证缓存项是否存在

    /// <summary>
    ///     验证缓存项是否存在
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public bool Exists(string key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));
        return _cache.KeyExists(GetKeyForRedis(key));
    }

    /// <summary>
    ///     验证缓存项是否存在
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public Task<bool> ExistsAsync(string key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));
        return _cache.KeyExistsAsync(GetKeyForRedis(key));
    }

    #endregion

    #region 添加缓存

    /// <summary>
    ///     添加缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <returns></returns>
    public bool Add(string key, object value)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _cache.StringSet(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)));
    }

    /// <summary>
    ///     添加缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <param name="expiresSliding">滑动过期时长（如果在过期时间内有操作，则以当前时间点延长过期时间,Redis中无效）</param>
    /// <param name="expiressAbsoulte">绝对过期时长</param>
    /// <returns></returns>
    public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _cache.StringSet(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)), expiressAbsoulte);
    }

    /// <summary>
    ///     添加缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <param name="expiresIn">缓存时长</param>
    /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间,Redis中无效）</param>
    /// <returns></returns>
    public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));


        return _cache.StringSet(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)), expiresIn);
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <returns></returns>
    public Task<bool> AddAsync(string key, object value)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _cache.StringSetAsync(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)));
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="expiresSliding"></param>
    /// <param name="expiressAbsoulte"></param>
    /// <returns></returns>
    public Task<bool> AddAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _cache.StringSetAsync(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)), expiressAbsoulte);
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="expiresIn"></param>
    /// <param name="isSliding"></param>
    /// <returns></returns>
    public Task<bool> AddAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        return _cache.StringSetAsync(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)), expiresIn);
    }

    /// <summary>
    ///     用键和值将某个缓存项插入缓存中，并指定基于时间的过期详细信息
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <param name="seconds">缓存时长</param>
    public bool Add(string key, object value, int seconds = 7200)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));


        var expiresIn = DateTime.Now.AddMinutes(seconds) - DateTime.Now;
        return _cache.StringSet(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions)), expiresIn);
    }

    #endregion

    #region List 操作

    /// <summary>
    ///     从底部插入数据
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <returns></returns>
    public long ListRightPush(string key, object value)
    {
        return _cache.ListRightPush(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions))); //从底部插入数据
    }

    /// <summary>
    ///     从顶部插入数据
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">缓存Value</param>
    /// <returns></returns>
    public long ListLeftPush(string key, object value)
    {
        return _cache.ListLeftPush(GetKeyForRedis(key),
            Encoding.UTF8.GetBytes(JsonSerializer.Serialize(value, _jsonOptions))); //从顶部插入数据
    }

    /// <summary>
    ///     从顶部拿取数据
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public string ListLeftPop(string key)
    {
        return _cache.ListLeftPop(GetKeyForRedis(key)); //从顶部插入数据
    }

    /// <summary>
    ///     从底部拿取数据
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public string ListRightPop(string key)
    {
        return _cache.ListRightPop(GetKeyForRedis(key)); //从顶部插入数据
    }

    #endregion

    #region 删除缓存

    /// <summary>
    ///     删除缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public bool Remove(string key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));
        return _cache.KeyDelete(GetKeyForRedis(key));
    }

    /// <summary>
    ///     批量删除缓存
    /// </summary>
    /// <param name="keys">缓存Key集合</param>
    /// <returns></returns>
    public void RemoveAll(IEnumerable<string> keys)
    {
        if (keys == null) throw new ArgumentNullException(nameof(keys));

        keys.ToList().ForEach(item => Remove(item));
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public Task<bool> RemoveAsync(string key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));
        return _cache.KeyDeleteAsync(GetKeyForRedis(key));
    }

    /// <summary>
    /// </summary>
    /// <param name="keys"></param>
    /// <returns></returns>
    public Task RemoveAllAsync(IEnumerable<string> keys)
    {
        //if (keys == null)
        //{
        throw new ArgumentNullException(nameof(keys));
        //}

        //keys.ToList().ForEach(item => RemoveAsync(item));
    }

    /// <summary>
    ///     使用通配符找出所有的key然后逐个删除
    /// </summary>
    /// <param name="pattern">通配符</param>
    public virtual void RemoveByPattern(string pattern)
    {
        foreach (var ep in _connection.GetEndPoints())
        {
            var server = _connection.GetServer(ep);
            var keys = server.Keys(pattern: "*" + pattern + "*", database: _cache.Database);
            foreach (var key in keys)
                _cache.KeyDelete(key);
        }
    }


    /// <summary>
    ///     删除所有缓存
    /// </summary>
    public void RemoveCacheAll()
    {
        RemoveByPattern("");
    }

    #endregion

    #region 获取缓存

    /// <summary>
    ///     获取缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public T GetByKey<T>(string key) where T : class
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        var value = _cache.StringGet(GetKeyForRedis(key));

        if (!value.HasValue) return default;

        return JsonSerializer.Deserialize<T>(value, _jsonOptions);
    }

    /// <summary>
    ///     获取缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <returns></returns>
    public object GetByKey(string key)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        var value = _cache.StringGet(GetKeyForRedis(key));

        if (!value.HasValue) return null;

        return JsonSerializer.Deserialize<object>(value, _jsonOptions);
        //string json = value.ToString();
        //return Newtonsoft.Json.JsonConvert.DeserializeObject(json);
    }

    /// <summary>
    ///     获取缓存集合
    /// </summary>
    /// <param name="keys">缓存Key集合</param>
    /// <returns></returns>
    public IDictionary<string, object> GetAll(IEnumerable<string> keys)
    {
        if (keys == null) throw new ArgumentNullException(nameof(keys));
        var dict = new Dictionary<string, object>();

        keys.ToList().ForEach(item => dict.Add(item, GetByKey(GetKeyForRedis(item))));

        return dict;
    }

    /// <summary>
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <returns></returns>
    public Task<T> GetAsync<T>(string key) where T : class
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <returns></returns>
    public Task<object> GetAsync(string key)
    {
        throw new NotImplementedException();
    }

    /// <summary>
    /// </summary>
    /// <param name="keys"></param>
    /// <returns></returns>
    public Task<IDictionary<string, object>> GetAllAsync(IEnumerable<string> keys)
    {
        throw new ArgumentNullException(nameof(keys));
    }

    #endregion

    #region 修改缓存

    /// <summary>
    ///     修改缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">新的缓存Value</param>
    /// <returns></returns>
    public bool Replace(string key, object value)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        if (Exists(key))
            if (!Remove(key))
                return false;

        return Add(key, value);
    }

    /// <summary>
    ///     修改缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">新的缓存Value</param>
    /// <param name="expiresSliding">滑动过期时长（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
    /// <param name="expiressAbsoulte">绝对过期时长</param>
    /// <returns></returns>
    public bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        if (Exists(key))
            if (!Remove(key))
                return false;

        return Add(key, value, expiresSliding, expiressAbsoulte);
    }

    /// <summary>
    ///     修改缓存
    /// </summary>
    /// <param name="key">缓存Key</param>
    /// <param name="value">新的缓存Value</param>
    /// <param name="expiresIn">缓存时长</param>
    /// <param name="isSliding">是否滑动过期（如果在过期时间内有操作，则以当前时间点延长过期时间）</param>
    /// <returns></returns>
    public bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false)
    {
        if (key == null) throw new ArgumentNullException(nameof(key));

        if (Exists(key))
            if (!Remove(key))
                return false;

        return Add(key, value, expiresIn, isSliding);
    }


    public Task<bool> ReplaceAsync(string key, object value)
    {
        throw new NotImplementedException();
    }

    public Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
    {
        throw new NotImplementedException();
    }

    public Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
    {
        throw new NotImplementedException();
    }

    #endregion

    #region lock

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="expiry"></param>
    /// <returns></returns>
    public (bool Success, string LockValue) Lock(string key, int expiry = 5000)
    {
        var lockValue = Guid.NewGuid().ToString();
        var expiration = TimeSpan.FromMilliseconds(expiry);
        var flag = _cache.LockTake(key, lockValue, expiration);
        return (flag, flag ? lockValue : string.Empty);
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="expiry"></param>
    /// <returns></returns>
    public async Task<(bool Success, string LockValue)> LockAsync(string key, int expiry = 5000)
    {
        var lockValue = Guid.NewGuid().ToString();
        var expiration = TimeSpan.FromMilliseconds(expiry);
        var flag = await _cache.LockTakeAsync(key, lockValue, expiration);
        return (flag, flag ? lockValue : string.Empty);
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="lockValue"></param>
    /// <returns></returns>
    public bool UnLock(string key, string lockValue)
    {
        return _cache.LockRelease(key, lockValue);
    }

    /// <summary>
    /// </summary>
    /// <param name="key"></param>
    /// <param name="lockValue"></param>
    /// <returns></returns>
    public async Task<bool> UnLockAsync(string key, string lockValue)
    {
        return await _cache.LockReleaseAsync(key, lockValue);
    }

    #endregion
}